package hyl.base.net.iot;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import hyl.core.MyFun;
import hyl.core.data.ExMap;
import hyl.core.net.IReader;
import hyl.core.net.NioClient;
import hyl.core.net.NioServer;
import hyl.core.run.IDoEachT1;
import hyl.core.run.MyRun;
import hyl.core.run.MyTimer;

/*
注册末端设备D 
Csrv 作为C的服务端 ,只支持 字符串通信,主要针对物联网设备
如果 需要byte包通信可以用Bsrv,已经规定好了包协议

:param imei:
:param 设备编号:  不要用中文 只能用字母和数字
:param conn:
:return:
*/
public class Csrv {
	String Cname;
	int Cid;
	NioClient Ccli;
	int Bid;
	NioServer Dsrv;
	ExMap<Integer, InfoValue<String>> D字典; // 只保存最临近的节点
	ExMap<IReader, Integer> D连接池;
	MyTimer ping;

	public static final String S间隔符0 = ";";
	public static final String S间隔符1 = "<]";
	public static final String S间隔符2 = "(]";
	public static final String S间隔符3 = "{]";
	String s间隔符 = S间隔符0;

	public Charset D编码;
	public Charset BA编码;
	// HEART_C 必须小于TIMEOUT_C
	public int HEART_C_MS = 60000; // 60s 心跳间隔时间
	public int TIMEOUT_C_MS = 120000; // 120s iC端连接超时(毫秒)
	public int TIMEOUT_C_S = 120; // 120s iC端连接超时(毫秒)
	public InetSocketAddress inetSocketA = null;// 上级服务的ip地址
	public InetSocketAddress inetSocketB = null;// 当前服务的ip地址

	/**
	 * 配置,通过system.ini获取
	 */
	public Csrv() {

		Cname = Conf.B_Servname;
		TIMEOUT_C_MS = Conf.TIMEOUT_C_MS;
		inetSocketA = Conf.inetSocketA;
		inetSocketB = Conf.inetSocketB;
		ini();

	}

	/**
	 * 传入参数 进行配置
	 * 
	 * @param cid
	 * @param ab
	 * @param caddr
	 */
	public Csrv(String c服务器名, int 超时毫秒, InetSocketAddress 上级服务器地址, InetSocketAddress 本机服务地址) {
		Cname = c服务器名;
		TIMEOUT_C_MS = 超时毫秒;
		inetSocketA = 上级服务器地址;
		inetSocketB = 本机服务地址;
		ini();

	}

	public void ini() {
		D编码 = Conf.D编码;
		BA编码 = Conf.BA编码;
		HEART_C_MS = TIMEOUT_C_MS / 2;
		TIMEOUT_C_S = TIMEOUT_C_MS / 1000;
		Cid = PFun.dname2Int(Cname);
		D字典 = new ExMap<>(TIMEOUT_C_MS);
		D连接池 = new ExMap<>(TIMEOUT_C_MS);
		D字典.set过期函数((过期键, info) -> {
			// 过期了,发送广播给上级
			sendtoAB(new int[] { 过期键, Cid }, Netpg3.CMD_D离线);
		});
		D连接池.set过期函数((reader, 过期键) -> {
			// 过期了,删除 该 socket
			reader.close();
		});

		Dsrv = new NioServer(inetSocketB);
		Dsrv.setReaderHandler(() -> {
			return new IDoEachT1<IReader, byte[]>() {
				public boolean isOver(IReader skt, byte[] b数据) {
					return receiveD(skt, b数据);

				};
			};
		});
		if (inetSocketA != null) {
			Ccli = new NioClient(inetSocketA);
			Ccli.setHandler((read, data) -> {
				receiveAB(read, data);
				return false;
			});
		}
		ping = MyTimer.getInstance();
	}

	public String getS间隔符() {
		return s间隔符;
	}

	public void setS间隔符(String s间隔符) {
		this.s间隔符 = s间隔符;
	}

	// ############################ B服务器 接受 C的接入##########################

	public IDoEachT1<IReader, byte[]> h上行前置器;
	public IDoEachT1<IReader, byte[]> h上行后置器;
	public IDoEachT1<IReader, byte[]> h上行注册器;
	public IDoEachT1<IReader, byte[]> h上行心跳器;

	boolean do后置器(IReader reader, byte[] bte) {
		if (h上行后置器 != null) {
			return h上行后置器.isOver(reader, bte);
		}
		return false;
	}

	boolean do注册器(IReader reader, byte[] bte) {
		if (h上行注册器 != null) {
			return h上行注册器.isOver(reader, bte);
		}
		return false;
	}

	boolean do心跳器(IReader reader, byte[] bte) {
		if (h上行心跳器 != null) {
			return h上行心跳器.isOver(reader, bte);
		}
		return false;
	}

	/**
	 * 注册接入终端C
	 * 
	 * @param 终端身份id
	 * @param 终端设备id
	 * @param 终端连接
	 */

	InfoValue<String> regD(String dname, IReader 终端C连接) {
		InfoValue<String> dInfo = new InfoValue<String>(dname, Cid, 终端C连接);
		System.out.println("" + Cid + ".regD(" + dInfo.id2 + ") " + 终端C连接.getRemoteAddress());
		D字典.put(dInfo.id2, dInfo);
		D连接池.put(终端C连接, dInfo.id2);
		int[] path = new int[] { dInfo.id2, Cid };
		终端C连接.getSession().put("info", dInfo);
		sendtoAB(path, Netpg3.CMD_D注册);
		return dInfo;
	}

	/**
	 * 收到C的反馈,c 终端 这里表示末端,只能收发 字符串 <br>
	 * 一定要防止字符串粘包,所以要规定指令的间隔符号<br>
	 * 可能有三种情况 第一次接入 ping入 应答
	 */
	boolean receiveD(IReader reader, byte[] bte) {

		if (h上行前置器 != null) {
			boolean flag = h上行前置器.isOver(reader, bte);
			if (flag)
				return true;
		}
		String string = MyFun.bytes2Str(bte, D编码);
		// 末端通信采用字符串格式,
		String[] strings = MyFun.split(string, s间隔符);

		for (String str : strings) {
			// AT指令对于服务端是无效的,只对通信模块配置有效

			if (!D连接池.containsKey(reader)) {
				// 第一个接收的字符串可以作为C端的别名
				regD(str, reader);
				@SuppressWarnings("unchecked")
				InfoValue<String> dInfo = (InfoValue<String>) reader.getSession().get("info");
				try {// 注册成功反馈 终端id
					String did = "did=" + dInfo.id2 + ";";
					reader.send(MyFun.str2Bytes(did, D编码));
				} catch (IOException e) {
				}
				return do注册器(reader, bte);
			}
			if (str.startsWith("AT")) {
				return false;
			}
			int cid = D连接池.get(reader);
			InfoValue<String> infod = D字典.get(cid);
			// ping的间隔时间必须小于TIMEOUT_C
			if (str.equals("ping")) {// 收到C端主动发送的心跳指令
				// 如果有上级服务器,就转给上级
				int[] path = new int[] { cid, Cid };
				sendtoAB(path, Netpg3.CMD_D心跳);
				return do心跳器(reader, bte);
			}
			// 以上是不需要转给B处理的逻辑, 相当于过滤
			// 以下是需要转给 B处理的指令
			// 要区别主动上传的数据和,下发以后反馈的数据

			//System.out.println("-receiveD() 1-" + str);
			boolean gl = infod.hasReply();
			//System.out.println("-receiveD() 1.1-" + gl);
			if (gl) {
				//System.out.println("-receiveD() 2-");
				infod.setReply(str);
				// System.out.println("-receiveD() 3-");
			} else {
				//System.out.println("-receiveD() 2.2-" + str);
				// 否则 是 没有等待情况下的反馈,属于主动反馈,这种特殊情况情况,交给上一次的发送端处理
				infod.netpg3.c结果编码 = BA编码;
				infod.netpg3.bb反馈 = MyFun.str2Bytes(str, BA编码);
				sendtoAB(infod.netpg3);
			}
			// 对于主动 反馈的消息
		}
		return do后置器(reader, bte);
	}

	protected void remove终端C(IReader reader, int cid) {
		D字典.remove(cid);
		D连接池.remove(reader);
		// My.rds_C.del(MyFun.str2Bytes(cid, My.BC编码));
		// log.info("断开客户端 ()",身份证id)
	}

	MyRun mr_ = MyRun.getInstance("csrv_send");

	public synchronized String sendToD(Netpg3 pg3) {

		if (pg3.b指令1 == Netpg3.CMD_文本 || pg3.b指令1 == Netpg3.CMD_字节) {
			IReader readerBC = null;
			int did = pg3.getDid();
			// System.out.println("sendToD() 1.0 "+did);
			try {
				InfoValue<String> info = D字典.get(did);
				// System.out.println("sendToD() 1.1 "+info);
				if (info == null)
					return null;
				readerBC = info.reader;
				info.netpg3 = pg3;
			
				//info.openReply();
				//System.out.println("send...");
				// 把指令发送给C端,通过 info.getReply() 方式等待信息反馈
				readerBC.send(MyFun.str2Bytes(pg3.s请求, D编码));
				//System.out.println("getReply...");
				String str = info.getReply();			
				
				// 等待3s 接收c的应答,如果没有应答转为null
				// 收到应答 立即转发给b
				pg3.c结果编码 = BA编码;
				// System.out.println("sendToD() 1.4 "+pg3.s请求);
				pg3.bb反馈 = MyFun.str2Bytes(str, BA编码);
				// System.out.println("sendToD() 1.5 "+pg3.s请求);
				sendtoAB(pg3);
				return str;
				// System.out.println("sendToD() 1.6 "+pg3.s请求);
			} catch (Exception e) {
				//e.printStackTrace();
				remove终端C(readerBC, did);
			}

		}
		return null;
	}

	// ############################ 连接 A服务器 ##########################
	/**
	 * 把指令转交给上级服务端
	 * 
	 * @param npNetpg3 数据
	 * @return
	 */
	public void sendtoAB(Netpg3 npNetpg3) {
		try {
			if (Ccli == null || npNetpg3 == null)
				return;
			// System.out.println("sendtoAB()"+JSON.toJSONString(npNetpg3.ii路径));
			/// if (PFun.getPath前(npNetpg3.ii路径, Cid) > 0)
			Ccli.send(npNetpg3.serial());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void sendtoAB(int[] 路径, byte cmd) {
		Netpg3 netpg3 = new Netpg3();
		netpg3.set(路径, cmd);
		sendtoAB(netpg3);
	}

	public void sendtoAB(int[] 路径, byte 消息类型, String 请求, Charset 反馈编码, byte[] 反馈内容) {

		Netpg3 netpg3 = new Netpg3();
		netpg3.set(路径, 消息类型, 请求, 反馈编码, 反馈内容);
		sendtoAB(netpg3);

	}

	public void connAB() {
		int[] 路径 = new int[] { Cid };
		sendtoAB(路径, Netpg3.CMD_注册);
	}

	/**
	 * 收到Bsrv发送的消息<br>
	 * 提取指令部分 转为文本发送给C终端
	 * 
	 */
	void receiveAB(IReader skt, byte[] data) {
		Netpg3 npNetpg3 = new Netpg3();
		npNetpg3.deserial(data);
		// System.out.println("csrv.receiveAB()"+npNetpg3);
		sendToD(npNetpg3);// # 转发送指令给设备C
	}

	/**
	 * pingB测试
	 */
	void pingAB() {
		if (Ccli == null)
			return;
		ping.start(() -> {
			try {
				sendtoAB(new int[] { Cid }, Netpg3.CMD_心跳);
			} catch (Exception e) {
				ta.interrupt();
				Ccli.close();
			}
		}, HEART_C_MS);
	}

	Thread ta, tb;

	// 入口程序
	public void start() {
		if (tb == null) {
			// 开启B服务,等待接收C消息,处理C发送的消息
			tb = Dsrv.start();
		}
		// 连接A服务,接收A的反馈
		if (Ccli == null)
			return;
		if (ta == null) {
			ta = Ccli.start();
		}
		connAB();
		pingAB();
	}

	public static void main(String[] args) {
		new Csrv().start();
	}

}
